package nz.ac.massey.jbuif.control;

import java.io.File;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.Collection;
import java.util.Vector;

import javax.swing.DefaultComboBoxModel;
import javax.swing.JOptionPane;

import nz.ac.massey.jbuif.editor.BeanEditor;
import nz.ac.massey.jbuif.io.IDataStoreAdapter;
import nz.ac.massey.jbuif.io.SerializedDataStoreAdapter;
import nz.ac.massey.jbuif.utils.JarClassLoader;
import nz.ac.massey.jbuif.utils.Utils;
import nz.ac.massey.jbuif.view.IView;

/**
 * class controller
 * @author AMIR H KIOUMARS
 *
 */
public class Controller {

	private Collection<Object> objects;
	private Vector<Object> objectHistory;

	private Collection<String> beansAvailable;

	private DefaultComboBoxModel bookMarkModel;
	private DefaultComboBoxModel beansAvailableModel;

	private IView view;
	public BeanEditor editor;

	private int historyIndex;

	private IDataStoreAdapter dataStore;

	private File file;

	private JarClassLoader jarClassLoader = null;

	
	/**
	 * default constructor
	 */
	public Controller() {
		// initialize the objects
		init();
	}
	

	/**
	 * set the file name
	 * @param file
	 */
	public void setFileName(File file) {
		this.file = file;
	}

	
	/**
	 * get the file name
	 * @return
	 */
	public File getFileName() {
		return file;
	}

	
	/**
	 * initialize all the parameters
	 */
	public void init() {
		dataStore = new SerializedDataStoreAdapter();

		// load available beans
		objects = new Vector<Object>();
		objectHistory = new Vector<Object>();

		beansAvailable = new Vector<String>();

		if (editor != null)
			editor.clearEditor();

		bookMarkModel = new DefaultComboBoxModel();
		beansAvailableModel = new DefaultComboBoxModel();

		initJarClassLoader();
		
		historyIndex = 0;
	}
	
	
	/**
	 * Gets the model that is used as the backing of the bookmark combo box.
	 * @return
	 */
	public DefaultComboBoxModel getBookMarkModel() {
		return bookMarkModel;
	}

	
	/**
	 * DefaultComboBoxModel
	 * @return available beans model
	 */
	public DefaultComboBoxModel getBeansAvailableModel() {
		return beansAvailableModel;
	}

	
	/**
	 * getObjects
	 * @return the collection of objects as a collection of objects
	 */
	public Collection<Object> getObjects() {
		return objects;
	}

	
	/**
	 * getAvailableBeans
	 * @return the available beans as a collection of string
	 */
	public Collection<String> getAvailableBeans() {
		return beansAvailable;
	}

	
	/**
	 * Saves the current set of objects to the data store. 
	 * @param file
	 */
	void saveObjects(File file) {
		if (file != null) {
			setFileName(file);
			dataStore.saveObjects(file, objects);
		}
	}

	void loadObjects(File file) {
		setFileName(file);
		objects.addAll(dataStore.loadObjects(file));
		view.setView(objects);
	}

	
	/**
	 * re-initialize the variables and clear the view
	 */
	void newProject() {
		view.clearView();
		editor.clearEditor();
		init();
	}

	
	/**
	 * Checks if there is a next entry in the history. 
	 * @return true if there is next entry, false otherwise.
	 */
	boolean historyHasNext()
	{
		boolean hasNext = historyIndex < objectHistory.size()-1;
//		System.out.println("history hasNext="+hasNext);
		return hasNext;
	}
	
	
	/**
	 * Checks if there is a previous entry in the history. 
	 * @return true if there is, false otherwise.
	 */
	boolean historyHasPrev()
	{
		boolean hasPrev = historyIndex > 0 && objectHistory.size() > 1;
//		System.out.println("history hasPrev="+hasPrev);
		return hasPrev;
	}
	
	
	/**
	 * Loads the next object from history into the bean editor.
	 * @return the bean that has been loaded into the editor.
	 */
	Object nextInHistory() {
		if (historyIndex < objectHistory.size()) {
			historyIndex++;
		} else {
			return null;
		}
		
//		System.out.println("historyIndex set to "+historyIndex);
		
		return editHistoryObject();
	}
	
	
	/**
	 * Loads the previous object from history into the bean editor.
	 * @return the bean that has been loaded into the editor.
	 */
	Object prevInHistory() {
		if (historyIndex > 0) {
			historyIndex--;
		} else {
			return null;
		}
		
//		System.out.println("historyIndex set to "+historyIndex);
		
		return editHistoryObject();
	}
	
	
	/**
	 * Loads the bean at the current history index into the bean editor.
	 * @return
	 */
	private Object editHistoryObject()
	{
		Object bean = objectHistory.get(historyIndex);
		
		editor.generateEditorForBean(bean, true);
		
		return bean;
	}

	
	/**
	 * Creates a new JarClassLoader that will be used for loading jars containing beans.
	 */
	private void initJarClassLoader() {
		ClassLoader oldCL = Thread.currentThread().getContextClassLoader();
		jarClassLoader = new JarClassLoader(new URL[] {}, oldCL);
		Thread.currentThread().setContextClassLoader(jarClassLoader);
	}

	
	/**
	 * Creates a new instance of the given bean class, and loads it into the editor.
	 * @param beanName the name of the bean to create.
	 */
	void createNewInstanceOfBean(String beanName) {
		// create a new instance of the bean
		Object beanInst = Utils.createBeanFromName(beanName, jarClassLoader);
		
		editor.generateEditorForBean(beanInst, false);
		editor.revalidate();
		
		addBean(beanInst);
	}
	
	
	/**
	 * add a bean
	 * @param bean object bean
	 */
	void addBean(Object bean)
	{
		this.objects.add(bean);
		view.setView(objects);
	}

	
	/**
	 * Reloads the view from the object collection.
	 */
	public void reloadView()
	{
		view.setView(objects);
	}
	
	
	/**
	 * load a jar file, extracts the bean classes, and add them to the available bean class list.
	 * @param file jar file
	 */
	@SuppressWarnings({ "unchecked", "deprecation" })
	void loadJar(File file) {

		// load up a bean jar file
		try {
			Collection<String> beansFromJar = Utils.loadJar(file,
					jarClassLoader);

			try{
				URL u = file.toURL();
				URLClassLoader urlClassLoader = (URLClassLoader) ClassLoader.getSystemClassLoader();
				Class urlClass = URLClassLoader.class;
				Method method = urlClass.getDeclaredMethod("addURL", new Class[]{URL.class});
				method.setAccessible(true);
				method.invoke(urlClassLoader, new Object[]{u});
			}
			catch(Exception e){}
			
			addBeans(beansFromJar);
			

		} catch (MalformedURLException e) {
			System.err.println("Cant load beans jar file "
					+ file.getAbsolutePath());
			e.printStackTrace();
		}
	}

	/**
	 * Adds the beans to the list of available beans, if there is not already a bean with the same absolute name.
	 * @param beans
	 */
	private void addBeans(Collection<String> beans) {
		for (String bean : beans) {
			if (!beansAvailable.contains(bean)) {
				beansAvailable.add(bean);
				getBeansAvailableModel().addElement(bean);
			}
		}
	}
	
	
	/**
	 * Remove beans from the available beans collection.
	 */
	@SuppressWarnings("unused")
	private void removeBeans(Collection<String> beans) {
		for (String bean : beans) {
			if (beansAvailable.contains(bean)) {
				beansAvailable.remove(bean);
				getBeansAvailableModel().removeElement(bean);
			}
		}
	}
	
	
	/**
	 * cloneBean clone an bean object
	 * @param bean object bean - object must be serializable
	 */
	void cloneBean(Object bean) {
		
		Object cloned = Utils.cloneObject(bean);

		if (cloned != null)
			addBean(cloned);
		
		else
			JOptionPane.showMessageDialog(null,"Cannot clone the object.", "Clone...", JOptionPane.OK_OPTION);
	}

	
	/**
	 * deleteBean delete a bean
	 * @param bean requested bean for delete 
	 */
	void deleteBean(Object bean) {
		// remove bean from bookmarks
		int bMrkIndex = bookMarkModel.getIndexOf(bean);
		if (bMrkIndex != -1) {
			bookMarkModel.removeElementAt(bMrkIndex);
		}

		// remove from history
		if (objectHistory.contains(bean)) {
			objectHistory.remove(bean);
		}

		// remove from collection of objects.
		if (this.objects.contains(bean)) {
			this.objects.remove(bean);
		}

		// if the editor is editing this bean, close it.
		if (bean.equals(editor.getBean())) {
			editor.clearEditor();
		}

		view.refreshView();
	}
	
	
	/**
	 * editBean edit a bean
	 * @param bean object for edit 
	 */
	void editBean(Object bean) {
		editor.generateEditorForBean(bean, false);
	}

	
	/**
	 * getBookmarkStatus
	 * @return the bookmark status as a boolean
	 */
	boolean getBookmarkStatus() {
		boolean status;
		if(getBeanInEditor() == null)
		{
			status = false;
		}
		else
		{
			status = (bookMarkModel.getIndexOf(getBeanInEditor()) != -1);
		}
		
		return status;
	}
	
	
	/**
	 * toggleBookmark
	 * @return the toggle bookmark status as a boolean
	 */
	boolean toggleBookmark() {
		// get bean in editor
		Object bean = getBeanInEditor();

		if (bean == null) {
			return false;
		}

		boolean bMrkStatus = false;
		int index = bookMarkModel.getIndexOf(bean);

		if (index != -1) {
			bookMarkModel.removeElementAt(index);
		} else {
			bookMarkModel.addElement(bean);
			bMrkStatus = true;
		}

		return bMrkStatus;
	}

	
	/**
	 * beanLoadedForEditing load a bean for edit
	 * @param bean a bean object
	 * @param isHistoryItem check whether its a history item
	 */
	void beanLoadedForEditing(Object bean, boolean isHistoryItem) {
		
		boolean isLastBean = false;
		if(objectHistory.size() > 0)
		{
			isLastBean = objectHistory.lastElement() == bean;
		}
		
		if (!isHistoryItem && !isLastBean) {
			// update the history
			objectHistory.add(bean);
			historyIndex = objectHistory.size()-1;
		}
		
		// set the bean to be selected in the view.
		view.setSelectedObject(bean);
	}

	
	/**
	 * getbeanInEditor
	 * @return the bean from editor
	 */
	public Object getBeanInEditor(){
		if(editor == null)
		{
			return null;
		}
		
		return editor.getBean();
	}
	
	
	/**
	 * setView set the view
	 * @param v instance of IView
	 */
	public void setView(IView v) {
		view = v;
	}
	
	
	/**
	 * setBeanEditor set the bean editor
	 * @param b a bean editor instance
	 */

	public void setBeanEditor(BeanEditor b) {
		editor = b;

		editor.setBeansAvailable(this.getAvailableBeans());
		editor.setBeanInstances(objects);
		editor.setClassLoader(this.jarClassLoader);
	}
}
